D:\a\csshw\csshw\src\protocol\deserialization.rs
Line | Count | Source |
1 | | use windows::{ |
2 | | core::BOOL, |
3 | | Win32::System::Console::{INPUT_RECORD_0, KEY_EVENT_RECORD, KEY_EVENT_RECORD_0}, |
4 | | }; |
5 | | |
6 | | use crate::protocol::{ |
7 | | ClientState, DaemonToClientMessage, SERIALIZED_INPUT_RECORD_0_LENGTH, SERIALIZED_PID_LENGTH, |
8 | | TAG_HIGHLIGHT, TAG_INPUT_RECORD, TAG_KEEP_ALIVE, TAG_STATE_CHANGE, |
9 | | }; |
10 | | |
11 | | /// Deserialize a [KEY_EVENT_RECORD_0] from a u8 slice using custom binary format. |
12 | | /// |
13 | | /// Tries to read a u16 from the given slice in little-endian format. |
14 | | /// |
15 | | /// Panics if reconstruction fails. |
16 | 1 | pub fn deserialize_key_event_record_0(slice: &[u8]) -> KEY_EVENT_RECORD_0 { |
17 | 1 | return KEY_EVENT_RECORD_0 { |
18 | 1 | UnicodeChar: u16::from_le_bytes([slice[0], slice[1]]), |
19 | 1 | }; |
20 | 1 | } |
21 | | |
22 | | /// Deserialize a [KEY_EVENT_RECORD] from a u8 slice using custom binary format. |
23 | | /// The slice is expected to be 13 bytes long. |
24 | | /// |
25 | | /// Layout: [1 byte KeyDown][2 bytes RepeatCount][2 bytes VirtualKeyCode] |
26 | | /// [2 bytes VirtualScanCode][2 bytes UnicodeChar][4 bytes ControlKeyState] |
27 | | /// |
28 | | /// Panics if reconstruction fails. |
29 | 6 | pub fn deserialize_key_event_record(slice: &[u8]) -> KEY_EVENT_RECORD { |
30 | 6 | return KEY_EVENT_RECORD { |
31 | 6 | // KeyDown (1 byte) |
32 | 6 | bKeyDown: BOOL::from(slice[0] != 0), |
33 | 6 | // RepeatCount (2 bytes LE) |
34 | 6 | wRepeatCount: u16::from_le_bytes([slice[1], slice[2]]), |
35 | 6 | // VirtualKeyCode (2 bytes LE) |
36 | 6 | wVirtualKeyCode: u16::from_le_bytes([slice[3], slice[4]]), |
37 | 6 | // VirtualScanCode (2 bytes LE) |
38 | 6 | wVirtualScanCode: u16::from_le_bytes([slice[5], slice[6]]), |
39 | 6 | // UnicodeChar (2 bytes LE) |
40 | 6 | uChar: KEY_EVENT_RECORD_0 { |
41 | 6 | UnicodeChar: u16::from_le_bytes([slice[7], slice[8]]), |
42 | 6 | }, |
43 | 6 | // ControlKeyState (4 bytes LE) |
44 | 6 | dwControlKeyState: u32::from_le_bytes([slice[9], slice[10], slice[11], slice[12]]), |
45 | 6 | }; |
46 | 6 | } |
47 | | |
48 | | /// Deserialize an [INPUT_RECORD_0].`KeyEvent` from a u8 slice using custom binary format. |
49 | | /// |
50 | | /// Panics if reconstruction fails. |
51 | 5 | pub fn deserialize_input_record_0(slice: &[u8]) -> INPUT_RECORD_0 { |
52 | 5 | let key_event = deserialize_key_event_record(slice); |
53 | 5 | return INPUT_RECORD_0 { |
54 | 5 | KeyEvent: key_event, |
55 | 5 | }; |
56 | 5 | } |
57 | | |
58 | | /// Deserialize a process id from its little-endian byte representation used |
59 | | /// by the named-pipe PID handshake. |
60 | 10 | pub fn deserialize_pid(bytes: &[u8; SERIALIZED_PID_LENGTH]) -> u32 { |
61 | 10 | return u32::from_le_bytes(*bytes); |
62 | 10 | } |
63 | | |
64 | | /// Deserialize a single byte into a [`ClientState`] variant. |
65 | | /// |
66 | | /// # Arguments |
67 | | /// |
68 | | /// * `byte` - The single payload byte of a [`crate::protocol::TAG_STATE_CHANGE`] |
69 | | /// frame, equal to a [`ClientState`]'s `#[repr(u8)]` discriminant. |
70 | | /// |
71 | | /// # Returns |
72 | | /// |
73 | | /// The decoded [`ClientState`]. |
74 | | /// |
75 | | /// # Panics |
76 | | /// |
77 | | /// Panics if `byte` does not match a known [`ClientState`] discriminant. An |
78 | | /// unknown value indicates either a protocol-version mismatch between the |
79 | | /// daemon and client or corruption on the pipe - both unrecoverable, matching |
80 | | /// the codebase's "broken bookkeeping -> panic" convention. |
81 | 6 | pub fn deserialize_client_state(byte: u8) -> ClientState { |
82 | 6 | match byte { |
83 | 6 | x3 if x == ClientState::Active as u8 => return ClientState::Active3 , |
84 | 3 | x2 if x == ClientState::Disabled as u8 => return ClientState::Disabled2 , |
85 | 1 | other => panic!("Unknown ClientState byte: 0x{other:02X}"), |
86 | | } |
87 | 5 | } |
88 | | |
89 | | /// Deserialize a single byte into a highlight flag. |
90 | | /// |
91 | | /// # Arguments |
92 | | /// |
93 | | /// * `byte` - Payload byte of a [`crate::protocol::TAG_HIGHLIGHT`] |
94 | | /// frame: `0` for not highlighted, `1` for highlighted. |
95 | | /// |
96 | | /// # Returns |
97 | | /// |
98 | | /// `true` if the byte signals a highlighted client, `false` otherwise. |
99 | | /// |
100 | | /// # Panics |
101 | | /// |
102 | | /// Panics on any byte other than `0` or `1` (protocol mismatch or |
103 | | /// pipe corruption). |
104 | 6 | pub fn deserialize_highlight(byte: u8) -> bool { |
105 | 6 | match byte { |
106 | 2 | 0 => return false, |
107 | 3 | 1 => return true, |
108 | 1 | other => panic!("Unknown highlight byte: 0x{other:02X}"), |
109 | | } |
110 | 5 | } |
111 | | |
112 | | /// Parse as many complete [`DaemonToClientMessage`]s as possible from `buffer`. |
113 | | /// |
114 | | /// The parser walks `buffer` from the start, decoding one tag-prefixed frame |
115 | | /// at a time. Parsing stops when fewer bytes remain than are needed to |
116 | | /// complete the next frame; the unconsumed tail is returned so the caller |
117 | | /// can prepend it to the next read. |
118 | | /// |
119 | | /// # Arguments |
120 | | /// |
121 | | /// * `buffer` - Bytes received from the daemon's named pipe, possibly |
122 | | /// including a partial trailing frame. |
123 | | /// |
124 | | /// # Returns |
125 | | /// |
126 | | /// A tuple of `(messages, remainder)` where `messages` are the fully decoded |
127 | | /// frames in arrival order and `remainder` holds the unconsumed bytes (an |
128 | | /// empty `Vec` if the buffer ended on a frame boundary). |
129 | | /// |
130 | | /// # Panics |
131 | | /// |
132 | | /// Panics if `buffer` contains a tag byte that is not part of the documented |
133 | | /// daemon-to-client protocol (see [`crate::protocol`]). An unknown tag |
134 | | /// indicates either a protocol-version mismatch between the daemon and |
135 | | /// client or corruption on the pipe -- both unrecoverable, matching the |
136 | | /// codebase's "broken bookkeeping -> panic" convention. |
137 | 12 | pub fn parse_daemon_to_client_messages(buffer: &[u8]) -> (Vec<DaemonToClientMessage>, Vec<u8>) { |
138 | 12 | let mut messages: Vec<DaemonToClientMessage> = Vec::new(); |
139 | 12 | let mut pos = 0usize; |
140 | 25 | while pos < buffer.len() { |
141 | 15 | let tag = buffer[pos]; |
142 | 15 | match tag { |
143 | | TAG_INPUT_RECORD => { |
144 | 5 | let payload_start = pos + 1; |
145 | 5 | let payload_end = payload_start + SERIALIZED_INPUT_RECORD_0_LENGTH; |
146 | 5 | if buffer.len() < payload_end { |
147 | | // Trailing partial frame; stop and return it as remainder. |
148 | 1 | break; |
149 | 4 | } |
150 | 4 | let record = deserialize_input_record_0(&buffer[payload_start..payload_end]); |
151 | 4 | messages.push(DaemonToClientMessage::InputRecord(record)); |
152 | 4 | pos = payload_end; |
153 | | } |
154 | | TAG_STATE_CHANGE => { |
155 | 3 | let payload_index = pos + 1; |
156 | 3 | if buffer.len() <= payload_index { |
157 | | // Trailing partial frame; stop and return it as remainder. |
158 | 0 | break; |
159 | 3 | } |
160 | 3 | let state = deserialize_client_state(buffer[payload_index]); |
161 | 3 | messages.push(DaemonToClientMessage::StateChange(state)); |
162 | 3 | pos = payload_index + 1; |
163 | | } |
164 | | TAG_HIGHLIGHT => { |
165 | 3 | let payload_index = pos + 1; |
166 | 3 | if buffer.len() <= payload_index { |
167 | | // Trailing partial frame; stop and return it as remainder. |
168 | 0 | break; |
169 | 3 | } |
170 | 3 | let highlighted = deserialize_highlight(buffer[payload_index]); |
171 | 3 | messages.push(DaemonToClientMessage::Highlight(highlighted)); |
172 | 3 | pos = payload_index + 1; |
173 | | } |
174 | 3 | TAG_KEEP_ALIVE => { |
175 | 3 | messages.push(DaemonToClientMessage::KeepAlive); |
176 | 3 | pos += 1; |
177 | 3 | } |
178 | | _ => { |
179 | 1 | panic!("Unknown daemon-to-client message tag: 0x{tag:02X}"); |
180 | | } |
181 | | } |
182 | | } |
183 | 11 | return (messages, buffer[pos..].to_vec()); |
184 | 11 | } |